我们知道,关于高DPI的支持,Windows XP时代就开始有了,那时关于高DPI的支持比较简单,但是从Vista/Win7 到现在Win8 /Win8.1,Windows关于高DPI的支持已经发生了很大的变化,下面我们依次简单介绍下。
如果说以前XP时代我们还有理由不关注高DPI,那么在移动设备时代和大显示器的高分辨率时代,我们就没有理由不关注高DPI了,比如Surface Pro的分辨率是1920x1080,这种情况下如果系统我们不设置高DPI,基本上就没法触摸和操作了,所以现在普通程序对高DPI的支持已经成为趋势了。
什么DPI?全称是dots per inch (DPI),也就是每英寸的点数,在显示器上就是每英寸的像素个数,Window上一般默认是96 dpi 作为100%的缩放比率,但是要注意的是该值未必是真正的显示器物理值,只是Windows里我们的一个参考标准。
下面我们思考为什么DPI设置高了之后,我们看到的字体会变大?因为系统字体是是以固定大小(宋体10号字,物理尺寸为(10/72)英寸)设计的,当我们DPI设置高了之后 ,说明该字体要占有更多的像素,在屏幕分辨率不变的前提下,看起来也就大了。所以如果我们设置高DPI,通常也意味着我们的显示器是高分辨率,里面的字体看起来太小了,我们需要提高DPI来把内容放大。
那么我们的程序如何才能支持高DPI?对于高DPI的支持,不同操作系统有不同的方案。通常来说如果我们程序支持高DPI,意味着我们要对绘画的内容进行相应的放大,比如字体,图片和控件等。当然,如果我们用的是系统字体(比如GetStockObject(DEFAULT_GUI_FONT)),那么这种情况下我们不用操心,因为系统会对该字体在高DPI时进行相应的放大;如果我们是用CreateFont自己创建的字体,那就要我们自己对该字体进行放大了。
下面我们看XP是如何对高DPI进行支持的?
XP对高DPI的支持比较差劲,大部分情况下就是字体的放大,当然我们程序也可以通过GetDeviceCaps(hDC,LOGPIXELSX)获取DPI后自己对绘画的内容进行缩放。
下面我们看Vista/Win7/Win8是如何对高DPI进行支持的?
我们知道Vista/Win7我们可以禁止DWM(Desktop Window Manager),该模式我们称之为Basic模式,这种模式下的高DPI效果和XP一样。
对于DWM没有禁掉的情况,Vista/Win7/Win8 对高DPI的支持又分为2种情况,具体看下图:
一种XP风格的高DPi支持,这种方式我们上面讨论过了;
还有一种是通过 DWM 虚拟化支持的高DPI方式,下面我们讨论下该方式:
该种方式的高DPI支持是通过DWM的缩放实现的,具体过程是这样的,比如我们当前系统的DPI是200%,我们程序运行时,系统会告诉你当前DPI仍然是96(100%),所以我们程序会仍然按照100%的方式进行绘画,但是但是系统给我们的坐标是根据DPI缩小过后的(也就是我们对窗口调用GetWindowRect或是通过GetSystemMetrics(SM_CXSCREEN)得到的大小会比实际大小减半),当我们画完之后,DWM再对整个窗口进行200%放大后画到屏幕上,这样看起来我们的程序就自动支持高DPI了。
这种方式看起来很美妙,但是它也有缺点,主要是经过缩放后的内容看起来会变模糊,比如文字会有明显的锯齿。
既然DWM虚拟化用户效果有时不是那么好,那么我们很多时候可能会自己支持高DPI,如何让我们的程序禁用该效果?
事实上我们可以对每个进程对DWM虚拟化的支持进行设置和查询,系统给我们提供了2个APi:SetProcessDPIAware 和IsProcessDPIAware ,通过调用SetProcessDPIAware ,我们告诉系统不要对我们的程序进行DWM虚拟化。
这里还有特殊情况也提一下:如果我们的程序支持DWM虚拟化,我们对其他支持DWM虚拟化的程序窗口调用GetWindowRect,取到的坐标也是经过DWM缩放后的坐标;对禁用DWM虚拟化程序的窗口调用GetWindowRect,取到的坐标则是没有经过缩放的原始坐标。坐标的变换可以通过 PhysicalToLogicalPoint 和LogicalToPhysicalPoint 来实现。
最后我们再讨论下Win8.1 对高DPI的支持,WIn8.1对高DPi以3种方式支持
Process_DPI_Awareness :typedef enum _Process_DPI_Awareness { Process_DPI_Unaware =0, Process_System_DPI_Aware =1, Process_Per_Monitor_DPI_Aware =2 }Process_DPI_Awareness;
下面我们依次讨论这3种方式:
第一种Unaware,该种方式是告诉系统,我的程序不支持DPI aware,请通过DWM虚拟化帮我们实现。该方式和上面Win7/Win8对高DPI的支持的实现基本一样,主要区别是它通过GetWindowRect取到的坐标都是经过DWM缩放后的,无论对方窗口是不是支持DWM虚拟化。
第二种方式是System DPI aware,该方式下告诉系统,我的程序会在启动的显示器上自己支持DPI aware,所以不需要对我进行DWM 虚拟化。但是当我的程序被拖动到其他DPI不一样的显示器时,请对我们先进行system DWM虚拟化缩放。
第三种方式是Per Monitor DPI aware,该方式是告诉系统,请永远不要对我进行DWM虚拟化,我会自己针对不同的Monitor的DPi缩放比率进行缩放。
再介绍下相关API:
SetProcessDpiAwareness :设置当前进程对高DPi的支持方式 GetProcessDpiAwareness :查询某个进程对高DPI的支持方式 GetDpiForMonitor :获取某个Monitor的DPI WM_DPICHANGED :当某个程序窗口被拖到另外一个DPI的Monitor时收到
最后,简单总结下,从上面我们可以看到微软在不同操作系统上对高DPI支持的改进线路,很多方面也体现了他们对老程序兼容性上的考虑,DWM虚拟化虽然很简单,却丢失了用户体验。
PS,我在我机器上测试发现,桌面程序基本上只有微软自己的程序能做到在高DPI下完美支持,其他大部分程序(即使如Chrome)也是通过DWM虚拟化实现的高DPI支持。当然现在WPF和Window store App基本上都是内置支持高DPI的。
统计下,你们的程序支持高DPI吗?
本页共42段,3040个字符,6510 Byte(字节)